// -*-Igor-*-
// Mac users can delete the next line if they get an error
#pragma version = 4.2
// 
// ###################################################################
//  Igor Pro - JEG Image Tools
// 
//  FILE: "JEG NanoLoader"
//                                    created: 9/25/96 {6:18:50 PM} 
//                                last update: 2/24/01 {3:18:46 PM} 
//  Author: Jonathan Guyer
//  E-mail: <jguyer@his.com>
//     WWW: <http://www.his.com/~jguyer/>
//  
//  Author: Dmitri Vezenov
//  E-mail: <dima@cmliris.harvard.edu>
//  
//  ========================================================================
//	          Copyright (c) 1997-1998 Dmitri Vezenov
//	          Copyright (c) 1996-2001 Jonathan Guyer
//  ========================================================================
//  Permission to use, copy, modify, and distribute this software and its
//  documentation for any purpose and without fee is hereby granted,
//  provided that the above copyright notice appear in all copies and that
//  both that the copyright notice and warranty disclaimer appear in
//  supporting documentation.
//
//  Jonathan Guyer and Dmitri Vezenov disclaim all warranties with regard
//  to this software, including all implied warranties of merchantability
//  and fitness.  In no event shall Jonathan Guyer or Dmitri Vezenov be
//  liable for any special, indirect or consequential damages or any
//  damages whatsoever resulting from loss of use, data or profits, whether
//  in an action of contract, negligence or other tortuous action, arising
//  out of or in connection with the use or performance of this software.
//  ========================================================================
//  
//  Description:
//
//      Loads NanoScope II, III, and CIAO image files.  X,Y, & Z are all
//      scaled properly.  Not extensively tested with every dain-bramaged
//      file format that NanoScope has come up with, but works with all
//      that I use.  I've tried to keep the NSII reader current, but the
//      NSIII and CIAO modules are the most up-to-date.  Assumes you're
//      reading AFM files (all I care about); tweaks are probably necessary
//      for STM, etc; please send changes to me and I'll include them in
//      future releases.
//
//      As of version 2.0, can load waves as raw integers rather than
//      scaled FP waves.  "JEG Color Legend" goes through some machinations
//      to apply the desired scaling to the waves.  I did this to reduce
//      the memory demands of the images; "a megabyte here and a megabyte
//      there and pretty soon you're talking about real memory".  Call
//      JEG_LoadNanoScope(0) if you want to use this behavior.
//                        ^
//      In conjunction with "JEG Spectral Filter", you'll need to use
//      non-integral waves, as the FFT converts to doubles by necessity and
//      then the data dimension scaling is lost.
//      
//      Images can now be exported as NanoScope data files, assuming the
//      wave note is properly formatted (which it will be if the wave was
//      generated by JEG_LoadNanoScope).  This allows you to do processing
//      or filtering with Igor and then reload the images into NanoScope.
//      
//      I have not had the opportunity to test this feature with the
//      NanoScope software.  BE SURE TO SAVE YOUR DATA in NanoScope
//      before attempting to use it to import files generated with this
//      package.  I can assume no responsibility for the stability of the
//      NanoScope software or for data loss that results from errors in
//      this export feature.  Please _do_ tell me if anything untoward
//      happens, however; I will do everything possible to ensure that bugs
//      are corrected expediently.
//      
//      Dmitri Vezenov added some logging functions in version 2.3. 
//      Unfortunately, these resulted in big chunks of his tools being
//      needed to even run these.  I've abstracted things a little.  If you
//      want to use these logging facilities, set the global strings:    
//          root:Packages:'JEG NanoLoader':S_JEG_UpdateImageHistoryProc
//      and
//          root:Packages:'JEG NanoLoader':S_JEG_RemoveFromHistoryProc
//      to the names of your logging procs ("UpdateImageHistory" and 
//      "RemoveFromHistory" in the case of DV's package).
//            
//  History
// 
//  modified   by  rev reason
//  ---------- --- --- -----------
//  1996-09-26 JEG 1.0 original
//  1996-12-15 JEG 2.0 Support loading images as integers
//  1997-03-02 JEG 2.1 Use "JEG Scale-Bar"
//  1998-06-15 DV  2.2 Added load of all other formats, 
//                     fixed the bug due to num2str() trip to 5 sig. fig. precision
//  1998-06-24 DV  2.3 Added image history
//  1998-08-02 JEG 3.0 Added export capability
//  1998-08-19 JEG 3.1 Support NanoScope CIAO format. Deprecated integer waves.
//  1998-10-29 JEG 4.0 Fully integrated JEG and DV variants.
//  1999-11-17 JEG 4.1 Removed dependance on DV tools (still supports them, though).
//                     Changed package name from "JEG Load Nanoscope" 
//                     to "JEG NanoLoader".
//  2001-02-24 JEG 4.2 Fix loading glitches in some intermediate NS III headers
// ###################################################################

#pragma rtGlobals=1		// Use modern global access method.

#include "JEG Keyword-Value" version >= 1.1
#include "JEG Color Legend" version >= 3
#include "JEG Scale-Bar"
#include "JEG Extract Dimensions"
#include "JEG Path Names"
#include <Strings as Lists>
#include <Autosize Images>
#include <String Substitution>

Menu "Load Waves"
	"Load NanoScope Image", JEG_LoadNanoScope("", 1, "JEG_DisplayNanoScope") 
	help = {"Load a NanoScope SPM file and display it."}
	// JEG_LoadNanoScope(0) to keep images as integers (saving memory)
End

Menu "Save Waves"
	"(-"
	"Save NanoScope Image", JEG_ExportNanoScope()
	help = {"Store an image as a NanoScope SPM binary file."}
End

Menu "New"
	"NanoScope Image", JEG_NanoScopeImage()
	help = {"Display an existing image wave in NanoScope format."}
End

//
// -------------------------------------------------------------------------
//   
// "JEG_NanoScopeImage" --
//  
//  Prompt for an existing wave and display as a NanoScope image
// -------------------------------------------------------------------------
//
Proc JEG_NanoScopeImage(imageName)
	String imageName
	Prompt imageName,"Image",popup WaveList("*", ";", "")
	
	Silent 1
	JEG_DisplayNanoScope_Image($imageName)
End

//
// -------------------------------------------------------------------------
//   
// "JEG_ExportNanoScope"    --
//  
//  Prompt for an existing wave and attempt to export it as a NanoScope file
// -------------------------------------------------------------------------
//
Proc JEG_ExportNanoScope(imageName)
	String imageName
	Prompt imageName,"Image",popup WaveList("*", ";", "")
	
	Silent 1
	JEG_DoNSExport(imageName)
End

//
// -------------------------------------------------------------------------
//   
// "JEG_LoadNanoScope" --
//  
//  Allow the user to select a data file and attempt to load it
//  as a NanoScope image file
// -------------------------------------------------------------------------
//
Function/S JEG_LoadNanoScope(theFilename, convertToFP, postProcess)
	String   theFilename, postProcess
	Variable convertToFP

	String df = GetDataFolder(1)
	NewDataFolder/O/S root:Packages
	NewDataFolder/O/S 'JEG NanoLoader'
	if (Exists("S_JEG_UpdateImageHistoryProc") != 2)
		String/G S_JEG_UpdateImageHistoryProc = ""
	endif
	if (Exists("S_JEG_RemoveFromHistoryProc") != 2)
		String/G S_JEG_RemoveFromHistoryProc = ""
	endif
	SetDataFolder df

	Variable NSfile
	String loadedWaves = ""
	
	if ( strlen(theFilename) == 0 )
		Open/D/T="????"/R/M="Select a NanoScope SPM image" NSfile
		theFilename = S_filename
	endif

	if ( strlen(theFilename) > 0 )
		Open/R/Z NSfile as theFilename
		if (V_flag)
			Abort "Error opening file " + theFilename + ".\rCheck the file name."
		endif
		
		string lf = num2char(10)
		string crlf = "\r" + lf
		string ctrlZ = num2char(26)
		
		String theHeader = ""
		
		// Scan for the header length
		
		Variable theHeaderSize
		String   theVersion
		Variable isNSIIfile = 0
		
		// Scan the header and see which type of NanoScope image this is
		
		// read in the whole header
		FReadLine /T=ctrlZ NSfile, theHeader
		// determine how long it's supposed to be
		theHeaderSize = JEG_NumByKey("\Data length", theHeader, ":", crlf)
		// get the hex version code
		theVersion = JEG_StrByKey("\Version: ", theHeader, "0x", crlf)
		// check for _old_ style format
		isNSIIfile = ( JEG_NumByKey("Data_File_Type", theHeader, " ", crlf) >= 7 )

		Close NSfile

		if ( isNSIIfile )
			loadedWaves = JEG_LoadNanoScopeII( theFilename, convertToFP )
		else
			if  ( str2num(theVersion) < 4300000 )	// previous to 4.3x
				string operatingMode = ""
				operatingMode = JEG_ParseNSIIIHeader( theHeader )
				
				Wave theHeaders = theHeaders
				
				String/G fileName = CleanupName( JEG_FileTail( theFileName ), 1 )
				if ( numpnts( theHeaders ) > 2 )
					NewDataFolder/O/S $fileName
					MoveWave ::theHeaders, :
				else
					String/G S_NSfileName = fileName
				endif
				
				String cmd
				if ( strlen( operatingMode ) > 0 )
					if ( cmpstr(operatingMode, "Force Volume") == 0 )
						cmd = "JEG_LoadNSIII_ForceVolumeFile( \"%s\", %d, \"%s\" ) \r"
						sprintf cmd, cmd, theFileName, convertToFP, postProcess
					else
						cmd = "JEG_LoadNSIII_SimpleFile( \"%s\", \"%s\", %d, \"%s\" ) \r"
						sprintf cmd, cmd, operatingMode, theFileName, convertToFP, postProcess
					endif
						Execute cmd
					
					SVAR S_loadedNSWaves = S_loadedNSWaves
					
					loadedWaves = S_loadedNSWaves
				endif
			else
				loadedWaves = JEG_LoadNanoScopeCIAO( theFilename, theHeaderSize, convertToFP )
			endif
		endif

	endif

	return loadedWaves		
End

//  NanoScope II 

//
// -------------------------------------------------------------------------
//   
// "JEG_LoadNanoScopeII" --
//  
//  Load the specified file as a NanoScope II image
//         
// --Version--Author------------------Changes-------------------------------  
//    1.0     <j-guyer@nwu.edu> original
//    2.0     <j-guyer@nwu.edu> load wave as raw integers, rather than scaled FP
// -------------------------------------------------------------------------
//
Function/S JEG_LoadNanoScopeII( theFilename, wasteMemory )
	String theFileName
	Variable wasteMemory
	
	Variable NSIIfile
	Open/R NSIIfile as theFilename
	
	String theHeader = ""
	theHeader = PadString(theHeader,2048,0x20)
	FBinRead NSIIfile, theHeader
	Close NSIIfile

	string lf = num2char(10)
	string crlf = "\r" + lf
	
					// discrete X-Y samples
	Variable num_samp = JEG_NumByKey("num_samp", theHeader, " = ", crlf)
					// X-Y size in nanometers
	Variable scan_sz = JEG_NumByKey("scan_sz", theHeader, " = ", crlf)
					// full vertical scale in nanometers
	Variable z_scale = JEG_NumByKey("z_scale", theHeader, " = ", crlf)

	Variable p1 = -1, p2 = -1
	do
		p2= strsearch(theFileName,":",p1+1)
		if( p2 >= 0 )
			p1 = p2
		endif
	while (p2 >= 0)
	
	String theImageName = CleanupName(theFileName[p1+1, strlen(theFileName)], 0)

			// Skip first 2048 header bytes and quietly load into wave named for file
	String s = "GBLoadWave/S=2048/Q/A=" + theImageName
	
	if (wasteMemory)
			// Load byte swapped, signed 16-bit data into single precision FP wave
			// read num_samp x num_samp data points
		s += "/B/T={16,2}/W=1/U=" + num2str(num_samp^2)
			// rescale integer data to proper z-scale in meters
		s += "/Y={0," + num2str(z_scale * 10^-9 / 16385) + "}"
	else
			// Load byte swapped, signed 16-bit data into signed 16-bit integer wave
			// read num_samp x num_samp data points
		s += "/B/T={16,16}/W=1/U=" + num2str(num_samp^2)
	endif
	
			// load from file...
		s += " \"" + theFileName + "\""
		
	Execute s
	
	SVAR theWaves = S_waveNames
	
	String theWave = GetStrFromList(theWaves, 0, ";")
	
	Redimension/N=(num_samp,num_samp) $theWave
	SetScale/I x, 0, scan_sz * 10^-9, "m", $theWave
	SetScale/I y, 0, scan_sz * 10^-9, "m", $theWave
	
	if (wasteMemory)
		SetScale d, -z_scale * 10^-9 / 2, z_scale * 10^-9 / 2, "m", $theWave
	else
		SetScale d, 0, z_scale *10^-9, "m", $theWave
	endif
	
	JEG_DisplayNanoScope_Image($theWave)

	Note $theWave, theHeader
	
	return theImageName + ";"
End

//  NanoScope III 

//
// -------------------------------------------------------------------------
//   
// "JEG_ParseNSIIIHeader" --
//  
//  Extract header information from NanoScope III image
//         
// --Version--Author------------------Changes-------------------------------  
//    2.1     <dima@cmliris.harvard.edu> original
//    4.0     <jguyer@his.com> integrated into other code
// -------------------------------------------------------------------------
//
Function/S JEG_ParseNSIIIHeader( theWholeHeader )
	String theWholeHeader

	string lf = num2char(10)
	string crlf = "\r" + lf
	string ctrlZ = num2char(26)
	
		// Determine the type of data
	String operatingMode= JEG_AbortingStrByKey("\Operating mode", theWholeHeader, ": ", crlf)
	String dataHeaderKey=""

		// Assign string identifying type of headers to read
	do
		if ( cmpstr(operatingMode, "Scope") == 0 )
			dataHeaderKey="\*Scope image"
			break
		endif
	
		if ( cmpstr(operatingMode, "Force") == 0 )
			dataHeaderKey="\*Force image"
			break
		endif

		if ( (cmpstr(operatingMode, "Image") == 0) %| (cmpstr(operatingMode, "Force Volume") == 0) )
			dataHeaderKey="\*NCAFM image"
				// If the Microscope is not a Multimode,
				// the header for image is different
			if ( strsearch(theWholeHeader, dataHeaderKey,  0) < 0 )
				dataHeaderKey="\*AFM image"
				// Blithering idiots at DI seem incapable of picking a #*$%! header
				// format and sticking with it for more than about 15 minutes!!!
				if ( strsearch(theWholeHeader, dataHeaderKey,  0) < 0 )
					dataHeaderKey="\*Image list"
				endif
			endif
			break
		endif

		Abort "This does not appear to be a NanoScope file.\rThis file format is not supported."
			
	while (0)
	
	Variable beginPosition = 0		// Beginning of string to copy
	Variable startPosition = 0		// Place to start looking for search string
	Variable endPosition = -1		// End of string to copy
	Variable numHeaders = 0			// Number of sub-headers in theWholeHeader
	Make/O/T theHeaders				// Text wave containing sub-headers

		// Split the header for data and parameter read into generic part and parts 
		// related to individual data sets
	do
		numHeaders += 1
		Redimension/N=(numHeaders) theHeaders

		endPosition = strsearch(theWholeHeader, dataHeaderKey,  startPosition) - 1

		if ( endPosition >= 0 )
			theHeaders[numHeaders - 1] = {theWholeHeader[beginPosition, endPosition]}
			beginPosition = endPosition + 1
			startPosition = beginPosition + strlen( dataHeaderKey )
			dataHeaderKey = "\*"			// allow to read all subsequent subheaders, 
											// e.g. force volume has a different subheader key than 
											// topographic channel
		else
			theHeaders[numHeaders - 1] = {theWholeHeader[beginPosition, Inf]}
			endPosition = -1
			// False #@*! header, only used in a few intermediate header versions
			if (strsearch(theHeaders[numHeaders - 1], "\*File list end", 0) >= 0)
				numHeaders -= 1
				Redimension/N=(numHeaders) theHeaders
			endif
		endif
	while ( endPosition >= 0 )

	return operatingMode
End

//  NanoScope III file loaders 

// 
// -------------------------------------------------------------------------
// 
// "JEG_LoadNSIII_SimpleFile" --
// 
//  Loads data from a NanoScope file of type operatingMode and post processes
//  as required
// 
// Results:
//  wave(s) created
// 
// Side effects:
//  Global S_loadedNSWaves lists waves that were loaded
// 
// --Version--Author------------------Changes-------------------------------
//    4.0     jguyer@his.com original
// -------------------------------------------------------------------------
// 
Function/S JEG_LoadNSIII_SimpleFile( operatingMode, theFilename, convertToFP, postProcess )
	String   operatingMode, theFilename
	Variable convertToFP
	String   postProcess

	String/G S_loadedNSWaves=""

	Variable traceNumber = 1

	Wave/T	theHeaders = theHeaders		// the sub-headers
	Variable numHeaders = numpnts( theHeaders )
	
	String prefix = ""
	
		// If this file contains multiple scans, then we've already been placed
		// in a data folder named after the file, so we just need to name the scans
		// appropriately. For just one scan, we need to name the scan something 
		// more verbose.
	if ( numHeaders == 2 )
		SVAR S_NSfileName = S_NSfileName
		prefix = CleanupName(S_NSfileName, 0) + "_"
	endif
	
	String cmd
	do
		cmd = "S_loadedNSWaves += JEG_LoadNSIII_%sData( %d, \"%s\", \"%s\", %d, 0, \"%s\" ) + \";\" \r"
		sprintf cmd, cmd, operatingMode, traceNumber, prefix, theFilename, convertToFP, postProcess
		Execute cmd
		traceNumber += 1
	while ( traceNumber < numHeaders )
	
	KillWaves/Z theHeaders	
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_LoadNSIII_ForceVolumeFile" --
// 
//  Loads data from a NanoScope file of type "Force Volume" and displays individual
//  images and traces if required.
// 
// Results:
//  Wave(s) created
// 
// Side effects:
//  Global S_loadedNSWaves lists waves that were loaded
// 
// --Version--Author------------------Changes-------------------------------
//    4.0     jguyer@his.com original
// -------------------------------------------------------------------------
// 
Function/S JEG_LoadNSIII_ForceVolumeFile( theFilename, convertToFP, postProcess )
	String   theFilename
	Variable convertToFP
	String   postProcess

	String/G S_loadedNSWaves=""

	Variable traceNumber = 1
	Wave/T	theHeaders = theHeaders		// the sub-headers
	Variable numHeaders = numpnts( theHeaders )
	
	String prefix = ""
	
		// If this file contains multiple scans, then we've already been placed
		// in a data folder named after the file, so we just need to name the scans
		// appropriately. For just one scan, we need to name the scan something 
		// more verbose.
	if ( numHeaders == 2 )
		SVAR S_NSfileName = S_NSfileName
		prefix = CleanupName(S_NSfileName, 0) + "_"
	endif
	
		// Scan each data header for important information, then load the data
	do
		if (strsearch(theHeaders[traceNumber], "\*Force image", 0) >= 0)
			// Form list of waves and display data if needed
			S_loadedNSWaves += JEG_LoadNSIII_ForceData( traceNumber, prefix, theFilename, convertToFP, 1, postProcess) + ";"
		else
			// Form list of waves and display data if needed
			S_loadedNSWaves += JEG_LoadNSIII_ImageData( traceNumber, prefix, theFilename, convertToFP, 1, postProcess) + ";"
		endif
		traceNumber += 1
	while ( traceNumber < numHeaders )
	
	KillWaves/Z theHeaders	
End

//  NanoScope III data loaders 

// 
// -------------------------------------------------------------------------
// 
// "JEG_LoadNSIII_ImageData" --
// 
//  Load a chunk of "Image" data
// 
// Results:
//  The name of the wave that was loaded
// 
// --Version--Author------------------Changes-------------------------------
//    4.0     jguyer@his.com original
// -------------------------------------------------------------------------
// 
Function/S JEG_LoadNSIII_ImageData( traceNumber, prefix, theFileName, convertToFP, fromFV, postProcess )
	Variable traceNumber, convertToFP, fromFV
	String   prefix, theFileName, postProcess

	Variable	dataOffset, dataLength					// where to find image in file
	Variable	sampsPerLine, numLines					// dimensions and scaling
	Variable	scanSize, zScale, scanSizeZ				// dimensions and scaling 

	String scanDimension, zDimension, scanDimensionZ	// dimensions and scaling
	String theDataName
	String cmd											// string holding the comand to be passed to Execute
	
	Wave/T	theHeaders = theHeaders						// the sub-headers

	String lf = num2char(10)
	String crlf = "\r" + lf

		// Starting location of image in file
	dataOffset = 	JEG_AbortingNumByKey("\Data offset", theHeaders[traceNumber], ":", crlf)
		// Length of image data (in bytes)
	dataLength = 	JEG_AbortingNumByKey("\Data length", theHeaders[traceNumber], ":", crlf)
		// Sample points per scan line
	sampsPerLine = 	JEG_AbortingNumByKey("\Samps/line", theHeaders[traceNumber], ":", crlf)
		// Number of lines
	numLines = 		JEG_AbortingNumByKey("\Number of lines", theHeaders[traceNumber], ":", crlf)
	
		// Form data name from data type (plus direction, if it is an image)
	theDataName =	prefix + JEG_AbortingStrByKey("\Image data", theHeaders[traceNumber], ": ", crlf)
	theDataName +=  "_" + JEG_AbortingStrByKey("\Line direction", theHeaders[traceNumber], ": ", crlf)

		// X-Y dimensions of an image
	scanDimension = JEG_AbortingStrByKey("\Scan size", theHeaders[traceNumber], ":", crlf) 
	
	// Some DI headers have useless redundant entry in scan size
	Variable v1, v2
	String s1
	sscanf scanDimension, "%e %e %s", v1, v2, s1
	if (strlen(s1) != 0)
		// Use the second number and the dimension string. Tough luck about the first number.
		sprintf scanDimension, "%e %s", v2, s1
	endif
	
	JEG_ExtractDimensions( scanDimension )
	NVAR V_dimensionValue = V_dimensionValue
	SVAR S_dimensionString = S_dimensionString
	scanSize = V_dimensionValue
	scanDimension = S_dimensionString
		// Z dimension
	zDimension = JEG_AbortingStrByKey("\Z scale", theHeaders[traceNumber], ":", crlf)
	JEG_ExtractDimensions(zDimension)
	zScale = V_dimensionValue
	zDimension = S_dimensionString
	
	if (convertToFP)
			// Skip first dataOffset bytes and quietly load into wave named temp
			// Load byte swapped, signed 16-bit data into single precision FP wave
			// read (dataLength / 2) bytes (2 bytes per point)
			// rescale integer data to proper z-scale units of zDimension
		cmd = "GBLoadWave/S=%.9g/Q/A=%s/B/T={16,2}/W=1/U=%.9g/Y={0,%.9g} \"%s\" \r"
		sprintf cmd, cmd, dataOffset, theDataName, dataLength / 2, zScale / 65535, theFileName
	else
			// Skip first dataOffset bytes and quietly load into wave named temp
			// Load byte swapped, signed 16-bit data into signed 16-bit integer wave
			// read (dataLength / 2) bytes (2 bytes per point)
		cmd = "GBLoadWave/S=%.9g/Q/A=%s/B/T={16,16}/W=1/U=%.9g \"%s\" \r"
		sprintf cmd, cmd, dataOffset, theDataName, dataLength / 2, theFileName
	endif

	Execute cmd
	
	SVAR theWaves = S_waveNames
	
		// Find out what Igor called it
	String theWaveName = GetStrFromList(theWaves, 0, ";")
	Wave theWave=$theWaveName		

	Redimension/N=(sampsPerLine, numLines) theWave
	SetScale/I x, 0, scanSize, scanDimension, theWave
	SetScale/I y, 0, scanSize, scanDimension, theWave
	
		// scaling to be applied by Z-legend
	if (ConvertToFP)
		SetScale d, -zScale / 2, zScale / 2, zDimension, theWave
	else
		SetScale d, 0, zScale, zDimension, theWave
	endif
	
		// Stick Nanoscope's file header in a wave note for later reference
	Note theWave, (theHeaders[0]+ theHeaders[traceNumber])			// generic header info plus info for this image
	JEG_UpdateImageHistory(theWave, theFilename)	
	
	if (strlen(postProcess) > 0)
		cmd = postProcess + "_Image"
		if (exists(cmd) != 6)
			Abort "The command " + cmd + "() is not defined. Aborting."
		else
			cmd = cmd + "(%s) \r"
			sprintf cmd, cmd, GetWavesDataFolder(theWave,2)
			Execute cmd
		endif
	endif
	
	return theWaveName
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_LoadNSIII_ForceData" --
// 
//  Load a chunk of "Force" data
// 
// Results:
//  The name of the wave that was loaded
// 
// --Version--Author------------------Changes-------------------------------
//    4.0     jguyer@his.com original
// -------------------------------------------------------------------------
// 
Function/S JEG_LoadNSIII_ForceData( traceNumber, prefix, theFileName, convertToFP, fromFV, postProcess )
	Variable traceNumber, convertToFP, fromFV
	String   prefix, theFileName, postProcess

	Variable	dataOffset, dataLength					// where to find image in file
	Variable	sampsPerLine, numLines					// dimensions and scaling
	Variable	scanSize, zScale, scanSizeZ				// dimensions and scaling 
	Variable	In1max, InputAttenuation, InSensitivity	// dimensions and scaling 
	Variable	Zsensitivity, Zmax						// dimensions and scaling 

	String scanDimension, zDimension, scanDimensionZ	// dimensions and scaling
	String theDataName
	String theTraceName, theRetraceName
	String cmd											// string holding the comand to be passed to Execute
	
	Wave/T	theHeaders = theHeaders						// the sub-headers

	String lf = num2char(10)
	String crlf = "\r" + lf

		// Starting location of image in file
	dataOffset = 	JEG_AbortingNumByKey("\Data offset", theHeaders[traceNumber], ":", crlf)
		// Length of image data (in bytes)
	dataLength = 	JEG_AbortingNumByKey("\Data length", theHeaders[traceNumber], ":", crlf)
		// Force data gets loaded as 1D wave, with twice number of points 
		// (both trace and retrace are saved)	
	sampsPerLine = 	2 * JEG_AbortingNumByKey("\Samps/line", theHeaders[traceNumber], ":", crlf)
		// Form data name from data type (plus direction, if it is am image)
	theDataName =	JEG_AbortingStrByKey("\Image data", theHeaders[traceNumber], ": ", crlf)
	
		// Z dimension of a FV  image
	Zsensitivity = 	JEG_AbortingNumByKey("\Z sensitivity", theHeaders[0], ":", crlf)
	Zmax = 			JEG_AbortingNumByKey("\Z max", theHeaders[0], ":", crlf)
	scanSizeZ = 	JEG_AbortingNumByKey("\Scan Size", theHeaders[traceNumber], ":", crlf) 
	scanSizeZ *= 	(Zmax * 2) * (Zsensitivity * 1e-9) / 65355
	scanDimensionZ = "m"
		// data dimension ( Zscale )
	In1max = 			JEG_AbortingNumByKey("\In1 max", theHeaders[0], ":", crlf)
	InputAttenuation = 	JEG_AbortingNumByKey("\Input attenuation mode", theHeaders[0], ":", crlf)
	InSensitivity  = 	JEG_AbortingNumByKey("\In sensitivity", theHeaders[0], ":", crlf)
	zScale = 			(In1max * 2) * InputAttenuation * InSensitivity
	zDimension = "V"

	if (fromFV)
			// Number of scan lines
		numLines = 		JEG_AbortingNumByKey("\force/line", theHeaders[0], ":", crlf)
			// X-Y dimension is the same as they are for topography image
		scanDimension = 	JEG_AbortingStrByKey("\Scan size", theHeaders[traceNumber -1], ":", crlf) 
		JEG_ExtractDimensions( scanDimension )
		NVAR V_dimensionValue = V_dimensionValue
		SVAR S_dimensionString = S_dimensionString
		scanSize = V_dimensionValue
		scanDimension = S_dimensionString
	else
			// Number of scan lines
		numLines = 0
		scanSize = scanSizeZ
		scanDimension = scanDimensionZ
	endif
	
	if (convertToFP)
			// Skip first dataOffset bytes and quietly load into wave named temp
			// Load byte swapped, signed 16-bit data into single precision FP wave
			// read (dataLength / 2) bytes (2 bytes per point)
			// rescale integer data to proper z-scale units of zDimension
		cmd = "GBLoadWave/S=%.9g/Q/A=%s/B/T={16,2}/W=1/U=%.9g/Y={0,%.9g} \"%s\" \r"
		sprintf cmd, cmd, dataOffset, theDataName, dataLength / 2, zScale / 65535, theFileName
	else
			// Skip first dataOffset bytes and quietly load into wave named temp
			// Load byte swapped, signed 16-bit data into signed 16-bit integer wave
			// read (dataLength / 2) bytes (2 bytes per point)
		cmd = "GBLoadWave/S=%.9g/Q/A=%s/B/T={16,16}/W=1/U=%.9g \"%s\" \r"
		sprintf cmd, cmd, dataOffset, theDataName, dataLength / 2, theFileName
	endif

	Execute cmd
	
	SVAR theWaves = S_waveNames
	
		// Find out what Igor called it
	String theWaveName = GetStrFromList(theWaves, 0, ";")
	Wave theWave=$theWaveName		

	if (fromFV)
		Redimension/N=(sampsPerLine, numLines, numLines) theWave
	else
		Redimension/N=(sampsPerLine, numLines) theWave
	endif
	SetScale/I x, 0, scanSize, scanDimension, theWave
	SetScale/I y, 0, scanSize, scanDimension, theWave
	
		// scaling to be applied by Z-legend
	if (ConvertToFP)
		SetScale d, -zScale / 2, zScale / 2, zDimension, theWave
	else
		SetScale d, 0, zScale, zDimension, theWave
	endif
	
		// Stick Nanoscope's file header in a wave note for later reference
	Note theWave, (theHeaders[0]+ theHeaders[traceNumber])			// generic header info plus info for this image
	JEG_UpdateImageHistory(theWave, theFilename)	
	
	if (fromFV)
		theTraceName = theDataName + "_trace"
		Duplicate/O/R=[0, (sampsPerLine / 2 - 1)] theWave, $theTraceName
		CopyScales/I theWave, $theTraceName
		theRetraceName=theDataName + "_retrace"
		Duplicate/O/R=[sampsPerLine / 2, (sampsPerLine-1)] theWave, $theRetraceName
		CopyScales/I theWave, $theRetraceName
		KillWaves/Z theWave
	else
			// Split traces in Force Volume file and make normal 3D waves with Z direction corresponding to Z direction of piezo
		theTraceName=theDataName + "_trace"
		theTraceName=UniqueName(theTraceName, 1, 0)
		Duplicate/O theWave, $theTraceName
		Wave trace = $theTraceName
		Redimension/N=(numLines, numLines, sampsPerLine/2)  trace
		trace[][][] = theWave[r][p][q]
		JEG_UpdateImageHistory(trace, theFilename)	
		JEG_RemoveFromHistory(trace, NameOfWave(theWave))
		JEG_UpdateImageHistory(trace, "volume")		

		theRetraceName = theDataName + "_retrace"
		theRetraceName = UniqueName(theRetraceName, 1, 0)
		Duplicate/O theWave, $theRetraceName
		Wave retrace = $theRetraceName
		Redimension/N=(numLines, numLines, sampsPerLine / 2)  retrace
		retrace[][][] = theWave[sampsPerLine / 2 + r][p][q]
		JEG_UpdateImageHistory(retrace, theFilename)	
		JEG_RemoveFromHistory(retrace, NameOfWave(theWave))
		JEG_UpdateImageHistory(retrace, "volume")		

		SetScale/I x, 0, scanSize, scanDimension, trace, retrace
		SetScale/I y, 0, scanSize, scanDimension, trace, retrace
		SetScale/I z, 0, scanSizeZ, scanDimensionZ, trace, retrace
		SetScale d, -zScale / 2, zScale / 2, zDimension, trace, retrace

		KillWaves/Z theWave
	endif

	if ((strlen(postProcess) > 0) %& !fromFV)
		cmd = postProcess + "_Force"
		if (exists(cmd) != 6)
			Abort "The command " + cmd + "() is not defined. Aborting."
		else
			cmd = cmd + "(%s, %s, \"%s\") \r"
			sprintf cmd, cmd, GetWavesDataFolder($theTraceName,2), GetWavesDataFolder($theRetraceName,2), theDataName
			Execute cmd
		endif
	endif

	return theTraceName + ";" + theRetraceName
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_LoadNSIII_ScopeData" --
// 
//  Load a chunk of "Scope" data
// 
// Results:
//  The name of the wave that was loaded
// 
// --Version--Author------------------Changes-------------------------------
//    4.0     jguyer@his.com original
// -------------------------------------------------------------------------
// 
Function/S JEG_LoadNSIII_ScopeData( traceNumber, prefix, theFileName, convertToFP, postProcess )
	Variable traceNumber, convertToFP
	String   prefix, theFileName, postProcess

	Variable	dataOffset, dataLength					// where to find image in file
	Variable	sampsPerLine, numLines					// dimensions and scaling
	Variable	scanSize, zScale, scanSizeZ				// dimensions and scaling 
	Variable	In1max, InputAttenuation, InSensitivity	// dimensions and scaling 
	Variable	Zsensitivity, Zmax						// dimensions and scaling 

	String scanDimension, zDimension, scanDimensionZ	// dimensions and scaling
	String theDataName
	String theTraceName, theRetraceName
	String cmd											// string holding the comand to be passed to Execute
	
	Wave/T	theHeaders = theHeaders						// the sub-headers

	String lf = num2char(10)
	String crlf = "\r" + lf

		// Starting location of image in file
	dataOffset = 	JEG_AbortingNumByKey("\Data offset", theHeaders[traceNumber], ":", crlf)
		// Length of image data (in bytes)
	dataLength = 	JEG_AbortingNumByKey("\Data length", theHeaders[traceNumber], ":", crlf)
		// Scope data gets loaded as 1D wave, with twice as many points as sampsPerLine
		// Sample points per scan line
	sampsPerLine = 	2 * JEG_AbortingNumByKey("\Samps/line", theHeaders[traceNumber], ":", crlf)
		// Form data name from data type (plus direction, if it is am image)
	theDataName =	JEG_AbortingStrByKey("\Image data", theHeaders[traceNumber], ": ", crlf)
		// Number of scan lines
	numLines = 0
	
		// X-Y dimensions of an image
	scanDimension = JEG_AbortingStrByKey("\Scan size", theHeaders[traceNumber], ":", crlf) 
	JEG_ExtractDimensions( scanDimension )
	NVAR V_dimensionValue = V_dimensionValue
	SVAR S_dimensionString = S_dimensionString
	scanSize = V_dimensionValue
	scanDimension = S_dimensionString
		// Z dimension
	zDimension = JEG_AbortingStrByKey("\Z scale", theHeaders[traceNumber], ":", crlf)
	JEG_ExtractDimensions(zDimension)
	zScale = V_dimensionValue
	zDimension = S_dimensionString
	
	if (convertToFP)
			// Skip first dataOffset bytes and quietly load into wave named temp
			// Load byte swapped, signed 16-bit data into single precision FP wave
			// read (dataLength / 2) bytes (2 bytes per point)
			// rescale integer data to proper z-scale units of zDimension
		cmd = "GBLoadWave/S=%.9g/Q/A=%s/B/T={16,2}/W=1/U=%.9g/Y={0,%.9g} \"%s\" \r"
		sprintf cmd, cmd, dataOffset, theDataName, dataLength / 2, zScale / 65535, theFileName
	else
			// Skip first dataOffset bytes and quietly load into wave named temp
			// Load byte swapped, signed 16-bit data into signed 16-bit integer wave
			// read (dataLength / 2) bytes (2 bytes per point)
		cmd = "GBLoadWave/S=%.9g/Q/A=%s/B/T={16,16}/W=1/U=%.9g \"%s\" \r"
		sprintf cmd, cmd, dataOffset, theDataName, dataLength / 2, theFileName
	endif

	Execute cmd
	
	SVAR theWaves = S_waveNames
	
		// Find out what Igor called it
	String theWaveName = GetStrFromList(theWaves, 0, ";")
	Wave theWave=$theWaveName		

	Redimension/N=(sampsPerLine, numLines) theWave
	SetScale/I x, 0, scanSize, scanDimension, theWave
	SetScale/I y, 0, scanSize, scanDimension, theWave
	
		// scaling to be applied by Z-legend
	if (ConvertToFP)
		SetScale d, -zScale / 2, zScale / 2, zDimension, theWave
	else
		SetScale d, 0, zScale, zDimension, theWave
	endif
	
		// Stick Nanoscope's file header in a wave note for later reference
	Note theWave, (theHeaders[0]+ theHeaders[traceNumber])			// generic header info plus info for this image
	JEG_UpdateImageHistory(theWave, theFilename)	
	
	theTraceName = theDataName + "_trace"
	Duplicate/O/R=[0, (sampsPerLine / 2 - 1)] theWave, $theTraceName
	CopyScales/I theWave, $theTraceName
	theRetraceName=theDataName + "_retrace"
	Duplicate/O/R=[sampsPerLine / 2, (sampsPerLine-1)] theWave, $theRetraceName
	CopyScales/I theWave, $theRetraceName
	KillWaves/Z theWave

	if (strlen(postProcess) > 0)
		cmd = postProcess + "_Scope"
		if (exists(cmd) != 6)
			Abort "The command " + cmd + "() is not defined. Aborting."
		else
			cmd = cmd + "(%s, %s, \"%s\") \r"
			sprintf cmd, cmd, GetWavesDataFolder($theTraceName,2), GetWavesDataFolder($theRetraceName,2), theDataName
			Execute cmd
		endif
	endif
	
	return theTraceName + ";" + theRetraceName
End

//  NanoScope CIAO 

//
// -------------------------------------------------------------------------
//   
// "JEG_LoadNanoScopeCIAO" --
//  
//  Load the specified file as a NanoScope image with CIAO header
//         
// --Version--Author------------------Changes-------------------------------  
//    3.1     <jguyer@his.com>  original
// -------------------------------------------------------------------------
//
Function/S JEG_LoadNanoScopeCIAO( theFilename, theHeaderSize, wasteMemory )
	String		theFilename
	Variable	theHeaderSize
	Variable	wasteMemory
		
	string lf = num2char(10)
	string crlf = "\r" + lf
	string ctrlZ = num2char(26)
	
	String loadedWaves = ""

	// Read the header of proper length
	
	Variable NSIIIfile

	String theWholeHeader = ""
	theWholeHeader = PadString( theWholeHeader, theHeaderSize, 0x20)
	Open/R NSIIIfile as theFilename
	FBinRead NSIIIfile, theWholeHeader
	Close NSIIIfile
	
	Variable beginPosition = 0	// Beginning of string to copy
	Variable startPosition = 0	// Place to start looking for search string
	Variable endPosition = -1	// End of string to copy
	Variable numHeaders = 0		// Number of sub-headers in theWholeHeader
	Make/O/T theHeaders			// Text wave containing sub-headers
	
	do
		numHeaders += 1
		Redimension/N=(numHeaders) theHeaders

		endPosition = strsearch(theWholeHeader, "\*Ciao image list",  startPosition) - 1
		
		if( endPosition >= 0 )
			theHeaders[numHeaders - 1] = {theWholeHeader[beginPosition, endPosition]}
			beginPosition = endPosition + 1
			startPosition = beginPosition + strlen( "\*Ciao image list" )
		else
			Variable endOfHeader = strsearch(theWholeHeader, ctrlZ,  startPosition) - 1
			if( endOfHeader == -1)
				Abort "This header is not properly terminated with a ctrl-z! Quitting."
			endif
			theHeaders[numHeaders - 1] = {theWholeHeader[beginPosition, endOfHeader]}
		endif
	while( endPosition >= 0)
	
//	Scan each image header for important information, then load the image

	Variable	dataOffset, dataLength						// where to find image in file
	Variable	sampsPerLine, numLines, scanSize, zScale	// dimensions and scaling 
	Variable	sensZscan, p1, p2, hardValue, softScale
	Variable	bytesPerPixel

	String		s, scanDimension							//  of image
	String		softScaleStr, softUnits, hardUnits
	Variable	temp

	Variable imageNumber = 1
	do
			// Starting location of image in file
		dataOffset = JEG_AbortingNumByKey("\Data offset", theHeaders[imageNumber], ":", crlf)
			// Length of image data (in bytes)
		dataLength = JEG_AbortingNumByKey("\Data length", theHeaders[imageNumber], ":", crlf)
			// Sample points per scan line
		sampsPerLine = JEG_AbortingNumByKey("\Samps/line", theHeaders[imageNumber], ":", crlf)
			// Scan lines per image
		numLines = JEG_AbortingNumByKey("\Number of lines", theHeaders[imageNumber], ":", crlf)
			// Bytes per Pixel
		bytesPerPixel = JEG_AbortingNumByKey("\Bytes/pixel", theHeaders[imageNumber], ":", crlf)

			// X-Y dimension of image
		s = JEG_AbortingStrByKey("\Scan size", theHeaders[imageNumber], ":", crlf)
		s = JEG_StripWhitespace(s, " \t\r\n")
		
		// compare this to the other value? 
		temp = str2num(GetStrFromList(s,0," "))
			
		JEG_ExtractDimensions(s[strsearch(s," ",0) + 1, Inf])
		NVAR V_dimensionValue = V_dimensionValue
		SVAR S_dimensionString = S_dimensionString
		scanSize = V_dimensionValue
		scanDimension = S_dimensionString

		// Z dimension
		s = JEG_AbortingStrByKey("\@2:Z scale", theHeaders[imageNumber], ":", crlf)
		
		// Extract the soft scale (we know it's "Sens. Zscan", but let's humor the
		// sadistic bastards at DI that came up with this mess)
		p1 = strsearch(s,"[",0) + 1
		p2 = strsearch(s,"]",p1) - 1
		softScaleStr = JEG_AbortingStrByKey("\@" + s[p1,p2], theHeaders[0], ":", crlf)
		softScaleStr = JEG_StripWhitespace(softScaleStr, " \t\r\n")
		// clip off the initial "V" and get the dimensions
		JEG_ExtractDimensions(softScaleStr[strsearch(softScaleStr," ",0) + 1, Inf])
		
		softScale = V_dimensionValue
		softUnits = S_dimensionString
		
		s = JEG_StripWhitespace(s, " \t\r\n")

		// jump past the hard scale (which is bogus) and get the value
		// crude. really needs a regexp
		s = s[strsearch(s,")",p2) + 1,Inf]
		
		JEG_ExtractDimensions(s)
		hardValue = V_dimensionValue
		hardUnits = S_dimensionString
		
		// make sure that we have something like 'm/V' and 'V', which resolve to 'm'
		if (cmpstr(hardUnits,GetStrFromList(softUnits,1,"/")) == 0)
			softUnits = GetStrFromList(softUnits,0,"/")
		else
			if (str2num(softUnits) == 1)
				// Potentially, softUnits could be some number besides 1, but if that's
				// the case, it'll be easier to give DI a beating than to try to build
				// a general parser
				
				softUnits = hardUnits
			else
				s = "Somebody at DI needs a smack. My simple mind doesn't know how "
				s += "to reconcile '" + softUnits + "' and '" + hardUnits + "'."
				
				Beep
				print s
				
				softUnits = "???"
			endif			
		endif
		
		p1 = -1
		p2 = -1
		do
			p2= strsearch(theFileName,":",p1+1)
			if ( p2 >= 0 )
				p1 = p2
			endif
		while (p2 >= 0)
		
		String theImageName = CleanupName(theFileName[p1+1, strlen(theFileName)], 1)
		
		if ( numHeaders > 2 )
			theImageName = CleanupName(theImageName + " image " + num2str(imageNumber), 1)
		endif

				// Skip first dataOffset bytes and quietly load into wave named temp
		s = "GBLoadWave/S=" + num2str(dataOffset) + "/Q/N=temp"
		
		if (wasteMemory)
				// Load byte swapped, signed integer data into single precision FP wave
			s += "/B/T={" + num2str(8 * bytesPerPixel) + ",2}/W=1"
				// read (dataLength / bytesPerPixel) values
			s += "/U=" + num2str(dataLength / bytesPerPixel)
				// rescale integer data to proper z-scale units
			s += "/Y={0," + num2str(hardValue * softScale / 2^(8 * bytesPerPixel)) + "}"
		else
				// Load byte swapped, signed integer data into signed integer wave
			s += "/B/T={" + num2str(8 * bytesPerPixel) + "," + num2str(8 * bytesPerPixel)+ "}"
				// read (dataLength / bytesPerPixel) bytes
			s += "/W=1/U=" + num2str(dataLength / bytesPerPixel)
		endif

			// load from file...
		s += " \"" + theFileName + "\""
		
		Execute s
		
		SVAR theWaves = S_waveNames
		
		// Find out what Igor called it
		String theWave = GetStrFromList(theWaves, 0, ";")
		
		Rename $theWave, $theImageName
		
		Redimension/N=(numLines,sampsPerLine) $theImageName
		SetScale/I x, 0, scanSize, scanDimension, $theImageName
		SetScale/I y, 0, scanSize, scanDimension, $theImageName
		
		// scaling to be applied by Z-legend
		if (wasteMemory)
			SetScale d, -(hardValue * softScale) / 2, (hardValue * softScale) / 2, softUnits, $theImageName
		else
			SetScale d, 0, (hardValue * softScale), softUnits, $theImageName
		endif
		
		// Stick NanoScope's file header in a wave note for later reference
		Note $theImageName, theHeaders[0]			// generic header info
		Note $theImageName, theHeaders[imageNumber]	// header info for this image
		
		imageNumber += 1
		
		loadedWaves += theImageName + ";"
		
		JEG_DisplayNanoScope_Image($theImageName)
	while ( imageNumber < numHeaders )

	KillWaves theHeaders

	return loadedWaves
End

//  NanoScope display routines 

Function JEG_DisplayNanoScope_Force(trace, retrace, dataName)
	Wave trace, retrace
	String dataName
	
	Display/K=1 trace
	AppendToGraph retrace
	Label left dataName
	Label bottom "Distance"
	AutoPositionWindow/E/M=1
End

Function JEG_DisplayNanoScope_Scope(trace, retrace, dataName)
	Wave trace, retrace
	String dataName
	
	Display/K=1 trace
	AppendToGraph retrace
	Label left dataName
	Label bottom "Distance"
	AutoPositionWindow/E/M=1
End

//
// -------------------------------------------------------------------------
//   
// "JEG_DisplayNanoScope_Image" --
//  
//  Display the image as NanoScope image with a data dimension scale bar
//  
// --Version--Author------------------Changes-------------------------------
//    2.0     <j-guyer@nwu.edu> original
//    2.1     <j-guyer@nwu.edu> use a "nice" scale bar
// -------------------------------------------------------------------------
//
Function JEG_DisplayNanoScope_Image(theImage)
	Wave theImage
	
	String theImageName = NameOfWave(theImage)
	// Display the plot
	Display as theImageName;  AppendImage theImage
	DoAutoSizeImage(0,1)
	ModifyGraph width={Aspect,1}

	// Comment out the next two lines if you really dig axis tick marks
	ModifyGraph tick=3, noLabel = 2, axThick = 0
	JEG_MakeScaleBar( "bottom", 0 )
	
	// Add a height scale-bar
	JEG_AddColorLegend2Graph( theImageName, 1, 1 )
End

//  NanoScope exporters 

// 
// -------------------------------------------------------------------------
// 
// "JEG_DoNSExport" --
// 
//  Attempt to export theImage as a NanoScope binary file. This assumes that
//  theImage was loaded in by JEG_LoadNanoScope, or was formatted the same way.
// 
// --Version--Author------------------Changes-------------------------------
//    3.0     jguyer@his.com original
// -------------------------------------------------------------------------
// 
Function JEG_DoNSExport(theImage)
	String theImage
	
	Variable NSfile
				
	Open/D/M="Save NanoScope SPM image as:" NSfile as theImage
	if ( cmpstr(S_filename, "") != 0 )
	
		String theHeader = note($theImage)
		Variable theHeaderSize
		Variable isNSIIfile = 0
		
		// Scan the header and see which type of NanoScope image this is
		
		string lf = num2char(10)
		string crlf = "\r" + lf

		theHeaderSize = JEG_NumByKey("\Data length", theHeader, ":", crlf)
		isNSIIfile = ( JEG_NumByKey("Data_File_Type", theHeader, " ", crlf) >= 7 )
		
		if ( isNSIIfile )
			JEG_ExportNanoScopeII( S_filename, $theImage, theHeader )
		else
			JEG_ExportNanoScopeIII( S_filename, theHeaderSize, $theImage, theHeader )
		endif
	endif
End

Function JEG_ExportNanoScopeII( theFilename, theImage, theHeader )
	String		theFilename
	Wave		theImage
	String		theHeader
	
	Abort "Sorry. No export routine for NanoScope II, yet."
End


//
// -------------------------------------------------------------------------
//   
// "JEG_ExportNanoScopeIII" --
//  
//  Export the specified image as a NanoScope III binary file
//         
// --Version--Author------------------Changes-------------------------------  
//    3.0     <jguyer@his.com>  original
// -------------------------------------------------------------------------
//
Function JEG_ExportNanoScopeIII( theFilename, theHeaderSize, theImage, theHeader )
	String		theFilename
	Variable	theHeaderSize
	Wave		theImage
	String		theHeader
		
	string lf = num2char(10)
	string crlf = "\r" + lf
	string ctrlZ = num2char(26)
	
	theHeader += ctrlZ

	// Many of the header items may no longer be correct if the image has been 
	// manipulated. We update them here

	Variable beginPosition = 0	// Beginning of string to copy
	Variable startPosition = 0	// Place to start looking for search string
	Variable endPosition = -1	// End of string to copy
	Variable numHeaders = 0		// Number of sub-headers in theWholeHeader
	Make/O/T theHeaders			// Text wave containing sub-headers

	// There should only be the master header and one image header, but who knows.
	// We'll ignore all but the first image header.
	do
		numHeaders += 1
		Redimension/N=(numHeaders) theHeaders

		endPosition = strsearch(theHeader, "\*NCAFM image list",  startPosition) - 1
		if ( endPosition >= 0 )
			theHeaders[numHeaders - 1] = {theHeader[beginPosition, endPosition]}
			beginPosition = endPosition + 1
			startPosition = beginPosition + strlen( "\*NCAFM image list" )
		else
			Variable endOfHeader = strsearch(theHeader, ctrlZ,  startPosition) - 1
			if ( endOfHeader == -1)
				Abort "This header is not properly terminated with a ctrl-z! Quitting."
			endif
			theHeaders[numHeaders - 1] = {theHeader[beginPosition, endOfHeader]}
		endif
	while ( endPosition >= 0)

		// Starting location of image in file
	theHeaders[1] = JEG_ReplaceNumByKey(theHeaders[1], "\Data offset", theHeaderSize, ": ", crlf)

		// Length of image data (in bytes)
	theHeaders[1] = JEG_ReplaceNumByKey(theHeaders[1], "\Data length", 2 * numpnts(theImage), ": ", crlf)
	
		// Sample points per scan line
	Variable scanPoints = DimSize(theImage,0)
	if (scanPoints != DimSize(theImage,1))
		Abort "The image must be square for NanoScope export"
		// Note: this may not be strictly true, but I'm not sure what needs
		// to be done for NanoScope to make sense of things. Safer this way.
	endif
	
	theHeaders[1] = JEG_ReplaceNumByKey(theHeaders[1], "\Samps/line", scanPoints, ": ", crlf)
	
		// Scan lines per image
	theHeaders[1] = JEG_ReplaceNumByKey(theHeaders[1], "\Number of lines", scanPoints, ": ", crlf)

	
	String s

		// X-Y dimension of image
		// We assume that X and Y are dimensioned identically and use X
	s = JEG_ApplyDimensions(scanPoints * DimDelta(theImage,0), WaveUnits(theImage,0))
	theHeaders[1] = JEG_ReplaceStrByKey(theHeaders[1], "\Scan size", s, ":", crlf)

		// Z dimension
	Variable zScale = 1
	String info = WaveInfo(theImage,0)
	String fullScale = JEG_StrByKey("FULLSCALE",info,":",";")
	if (str2num(GetStrFromList(fullScale,0,",")) == 1)
		zScale  = str2num(GetStrFromList(fullScale,2,","))		// data max full scale 
		zScale -= str2num(GetStrFromList(fullScale,1,","))		// data min full scale
	else
		WaveStats/Q theImage
		zScale = V_max - V_min
	endif
		
	s = JEG_ApplyDimensions(zScale, WaveUnits(theImage,-1))
	theHeaders[1] = JEG_ReplaceStrByKey(theHeaders[1], "\Z scale", s, ":", crlf)

	Variable NSIIIfile

	// Write the header of proper length.
	// In the event that theImage was loaded from a multi-image file, the output
	// header may be inordinately long, but it should cause no harm.

	theHeader = PadString( theHeaders[0] + theHeaders[1] + ctrlZ, theHeaderSize, 0x00)

	Open/T="IGBW" NSIIIfile as theFilename
	FBinWrite NSIIIfile, theHeader
	Close NSIIIfile
		
	// Make a copy, because we may need to redimension it
	Duplicate theImage, JEG_NSExportWave
	
	// Image may have been redimensioned to reals on import [JEG_LoadNanoScope(1)]
	if (WaveType(JEG_NSExportWave) != 0x10)
		// convert real data to scaled, 16-bit integer values
		JEG_NSExportWave *= 2^16 / zScale
		Redimension/W JEG_NSExportWave
	endif
	
	Open/T="IGBW"/A NSIIIfile as theFilename

	// write byte swapped, signed 16-bit data
	FBinWrite/B/F=2 NSIIIfile, JEG_NSExportWave
	
	Close NSIIIfile
	
	// Get rid of our temporary waves
	KillWaves JEG_NSExportWave
	KillWaves theHeaders
End


// 
// -------------------------------------------------------------------------
// 
// "JEG_ApplyDimensions" --
// 
//  Format value as a number followed by units, with appropriate SI prefix.
//  Translate "" to "~" for idiot DOS applications.
// -------------------------------------------------------------------------
// 
Function/S JEG_ApplyDimensions(value,units)
	Variable value
	String units
	
	String s 
	sprintf s, " %W1P%s", value, units
	
	Variable i = 0
	// There shouldn't ever be more than one, but let's be thorough
	do
		i = strsearch(s, "", i)
		if (i != -1)
			s[i,i] = "~"
		endif
	while (i >= 0)
	
	return s
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_ProtoUpdateImageHistory" --
// 
//  Dummy proc
// 
// Results:
//  None
// 
// Side effects:
//  None
// -------------------------------------------------------------------------
// 
Function JEG_UpdateImageHistory(image, action)
	Wave   image
	String action
	
	SVAR UpdateImageHistory = root:Packages:'JEG NanoLoader':S_JEG_UpdateImageHistoryProc
	
	if (strlen(UpdateImageHistory) > 0)
		String s
		sprintf s, "%s(%s, \"%s\")", UpdateImageHistory, GetWavesDataFolder(image,2), action
		Execute s
	endif
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_ProtoRemoveFromHistory" --
// 
//  Dummy proc
// 
// Results:
//  None
// 
// Side effects:
//  None
// -------------------------------------------------------------------------
// 
Function JEG_RemoveFromHistory(image, itemToRemove)
	Wave   image
	String itemToRemove

	SVAR RemoveFromHistory = root:Packages:'JEG NanoLoader':S_JEG_RemoveFromHistoryProc
	
	if (strlen(RemoveFromHistory) > 0)
		String s
		sprintf s, "%s(%s, \"%s\")", RemoveFromHistory, GetWavesDataFolder(image,2), itemToRemove
		Execute s
	endif
end
